package me.itsoo.artemis.framework.core.filter;

import lombok.ToString;

import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Objects;
import java.util.Set;

/**
 * TrieFilter
 *
 * @author zxy
 * @version 0.0.1
 * @since 2023/5/1
 */
public class TrieFilter implements Filter<String> {

    private final TrieNode root = new TrieNode('\u0000');

    private TrieFilter(Collection<String> dict) {
        final Collection<String> dictColl = (dict == null) ? Collections.emptySet() : dict;

        this.init(dictColl);
    }

    @SuppressWarnings("squid:S3776")
    private void init(Collection<String> dictColl) {
        for (String s : dictColl) {
            TrieNode curr = this.root;

            for (int i = 0, len = s.length(); i < len; ++i) {
                final TrieNode node = new TrieNode(s.charAt(i));

                if (curr.children.add(node)) {
                    node.isEnd = (i == s.length() - 1);
                    curr = node;
                } else {
                    for (TrieNode childNode : curr.children) {
                        if (childNode.equals(node)) {
                            childNode.isEnd = (!childNode.isEnd && i == s.length() - 1);
                            curr = childNode;
                            break;
                        }
                    }
                }
            }
        }
    }

    /**
     * 创建 TrieFilter
     *
     * @param dict Collection
     * @return TrieFilter
     */
    public static TrieFilter create(Collection<String> dict) {
        return new TrieFilter(dict);
    }

    @Override
    public boolean match(String str) {
        for (int i = 0, len = str.length(); i < len; ++i) {
            for (TrieNode node : this.root.children) {
                if (this.contains(str, i, node)) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * 是否匹配内容
     *
     * @param str  String
     * @param i    offset
     * @param node TrieNode
     * @return boolean
     */
    private boolean contains(String str, int i, TrieNode node) {
        if (i < str.length() && node.equals(str.charAt(i))) {
            if (node.isEnd) {
                return true;
            }

            for (Iterator<TrieNode> it = node.children.iterator(); it.hasNext(); --i) {
                final TrieNode childNode = it.next();
                ++i;
                if (this.contains(str, i, childNode)) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * TrieNode
     */
    @ToString
    private static class TrieNode {

        private final char literal;

        private final Set<TrieNode> children;

        private boolean isEnd;

        private TrieNode(char literal) {
            this.literal = this.toLower(literal);
            this.children = new LinkedHashSet<>();
            this.isEnd = false;
        }

        @Override
        public int hashCode() {
            return Objects.hash(this.literal);
        }

        @Override
        public boolean equals(Object tar) {
            return (tar instanceof Character) ? this.equals((char) tar) : (tar instanceof TrieNode) && this.equals((TrieNode) tar);
        }

        private boolean equals(char tar) {
            return this.literal == this.toLower(tar);
        }

        private boolean equals(TrieNode tar) {
            return (this == tar) || (tar != null && this.literal == this.toLower(tar.literal));
        }

        private char toLower(char tar) {
            return Character.isUpperCase(tar) ? ((char) (tar + 32)) : tar;
        }
    }
}
